The Java programming language has been developed to facilitate the development of programs that can be executed on a wide variety of computers, independent of the computers' particular system or processor architecture. The Java programming language is generally object-oriented, and a program developer generates a Java program in the form of one or more classes. A Java compiler ("javac") compiles the program into Java byte code ("JBC") for distribution. When the Java program is to be executed on a computer, the Java bye code can be executed in an interpretive manner by a Java "virtual machine" which is provided on the computer. Alternatively, the Java byte code can be compiled to the native instruction code of the computer on which the Java program is to be executed by, for example, a "just-in-time" ("JIT") compiler. The just-in-time compiler provides the compiled code to the Java virtual machine for execution. In processing the Java program, the Java virtual machine provides a number of services, including interfacing with the computer's operating system to, for example, obtain data ("input") for processing and provide processed data ("output") for storage, display to an operator and so forth.
A problem arises in connection with Java programs in which access to a shared variable must be synchronized among a plurality of threads which may be able to access the shared variable. This problem will be illustrated with the following example. Suppose the source code of a Java program includes Code Segment 1:
______________________________________ Code Segment 1 ______________________________________ class tock { int Ticket; int Other; int getTicket ( ) { int rslt; synchronized (this) { rslt = ++Ticket; return rslt; } int getUnsafe ( ) { int rslt ; rslt = ++Other; return rslt ; } } ______________________________________
Code Segment 1 represents a Java class, named "tock," which includes two methods named "getTicket" and "getUnsafe." The method "getTicket" can be called to obtain a result "rslt" which is the increment the value ofthe variable "Ticket." The incrementation ofthe variable "Ticket" and the assignment to the result "rslt" is represented by the instruction "rslt=++Ticket". The statement "synchronized (this)" provides that the incrementation and assignment will be executed in a synchronized manner, that is, in a manner that guarantees that, when a thread calls the method "getTicket," other threads will not be able to access the variable "Ticket" during the incrementation and assignment.
The method "getUnsafe" can also be called to obtain a result "rslt" which is the whose value represents the incrementation of another variable, namely, "Other." However, since the method "getUnsafe" does not include a "synchronized ()" statement, the incrementation and assignment will not be executed in a manner to guarantee that, when a thread calls the method "getUnsafe," other threads will not be able to access the variable "Other" during the incrementation and assignment. This will be described in connection with Code Segment 2. Code Segment 2 depicts code, in native code form, which the Java just-in-time compiler generate for the "++ Other" expression in Code Segment 1. If the computer's native code is the SPARC instruction set (reference SPARC International, Inc [David L. Weaver and Tom Germond (eds)], The SPARC Architecture Manual Version 9 (Prentice-Hall, 1994)), the just-in-time compiler would generate native code along the lines of Code Segment 2:
Code Segment 2
(1) ld [o0].Other, o1 PA0 (2) add o1, 1, o1 PA0 (3) st [o1], [o0].Other
When the computer executes Code Segment 2, it retrieves a value stored in the storage location identified by one of its internal registers and loads the value into another register (line 1), adds the value "one" to the contents of the other register, thereby to increment the value stored therein (line 2), and stores the incremented value in the original storage location (line 3). However, if the thread of execution which includes Code Segment 2 is pre-empted after line 1 is executed, but before line 3 is finished, to allow another thread to execute, the value stored in the storage location may be modified by another thread, which can cause problems when the one thread (that is, the thread which includes Code Segment 2) resumes execution. Similarly, if the computer is a multi-processor system, in which one processor is executing the thread that includes Code Segment 2 and another processor is executing another thread, the other processor may modify the value in the storage location after the one processor (that is, the processor executing the threat that includes Code Segment 2) executes line 1 but before it finishes executing line 3, problems can similarly ase.
Although avoiding the use of the "synchronized ()" statement, as in the "getUnsafe" method described above, can lead to the undesirable consequence that other threads can have access to a variable that is shared among a plurality of threads, use of the "synchronized ()" statement, as in the "getTicket" method described above, can also lead to an undesirable consequence. In particular, the "synchronized ()" statement in the source code typically is transformed into synchronization primitives which can take a relatively long time to execute. Thus, a "synchronized ()" statement in the source code can require a relatively long time for the computer to process. This will be shown in the following. The Java compiler compiles Code Segment 1 into Java Byte Code represented by Code Segment 3:
______________________________________ Code Segment 3 ______________________________________ class tock extends java.lang.Object { int Ticket; Int Other; tock ( ); int getUnsafe ( ); int getTicket ( ); } Method tock ( ) 0 aload.sub.-- 0 1 invokespecial #3 &lt;Method java.lang.Object&gt; 4 return Method int getUnsafe ( ) 0 aload.sub.-- 0 1 dup 2 getfield #4 &lt;Field int Other&gt; 5 iconst.sub.-- 1 6 iadd 7 dup.sub.-- x1 8 putfield #4 &lt;Field int Other&gt; 11 istore.sub.-- 1 12 iload.sub.-- 1 13 ireturn Method int getTicket( ) 0 aload.sub.-- 0 1 astore.sub.-- 2 2 aload.sub.-- 2 3 monitorenter 4 aload.sub.-- 0 5 dup 6 getfield #5 &lt;Field int Ticket&gt; 9 iconst.sub.-- 1 10 iadd 11 dup.sub.-- x1 12 putfield #5 &lt;Field int Tidket&gt; 15 istore.sub.-- 1 16 aload.sub.-- 2 17 monitorexit 18 goto 24 21 aload.sub.-- 2 22 monitorexit 23 athrow 24 iload.sub.-- 1 25 ireturn Exception table: from to target type 4 16 21 any ______________________________________
In Code Segment 3, Java Byte Code, emitted by javac (the Java compiler) is provided for both the "getTicket" method and the "getUnsafe" method for the compiled tock class. The code for the "getUnsafe" method is provided in the portion of Code Segment 3 immediately following the line "Method int getUnsafe ()" and the code for the "getTicket method is provided in the portion of Code Segment 3 immediately following the line "Method int getTicket ()." The code for the two methods has a number similarities, in particular, the code for most of the "getUnsafe" method, in particular lines 0 through 12, essentially corresponds to lines 4 through 16 of the "getTicket" method. The code in these lines essentially controls the incrementation and assignment functions for both methods. The "getTicket" method also has, immediately preceding and following this code, a "monitorenter" ("monitor enter") code (line 3) and a "monitorexit" ("monitor exit") code (line 17), which is provided in response to the "synchronized ()" statement in the source code (Code Segment 1).
As noted above, prior to execution by a computer, the Java Byte Code of Code Segment 3 can be compiled by the just-in-time compiler into the computer's native code. For the portion of Code Segment 3 provided for the "getTicket" method, if the computer's native code is the SPARC instruction set, the just-in-time compiler would generate native code along the lines of Code Segment 4:
______________________________________ Code Segment 4 ______________________________________ (1) Tock$getTicket (2) save %sp, -64, %sp !!subroutine enter (3) prolog (4) ld [%i0].ObjectLock, %10 !!get lock variable (5) call mutex.sub.-- enter !!begin synchroni- zation (6) mov %10, %o0 (7) ld [%i0].Ticket, %o0 (8) add %o0, 1, %o0 !!increment Ticket (9) st %o0, [%i0].Ticket (10) call mutex.sub.-- exit !!.end synchroni- zation (11) mov %10, %o0 (12) ret !!subroutine exit (13) epilog (14) restore ______________________________________
In Code Segment 4, the just-in-time compiler makes use of synchronization primitives "mutex.sub.-- enter" ("mutual exclusion enter") and "mutex.sub.-- exit" ("mutual exclusion exit") to provide the synchronization to the variable "Ticket." However, each of the "mutex.sub.-- enter" and "mutex.sub.-- exit" synchronization primitives requires execution of a large number of instructions, in some cases requiring calls to the operating system kernel. Thus, use of such synchronization primitives can result in relatively long processing times.